using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using SpoonStun.Message;

namespace SpoonStun
{
	public class StunClient
	{
		private const int HeaderSize = 4;
		private Socket _socket;
		private StunServer _server;
		private byte[] _buffer = new byte[65536];
		private MemoryStream _dataStream = new MemoryStream();

		public delegate void OnMessageReceiveHandler(StunClient client, byte[] msg);

		public event OnMessageReceiveHandler OnMessageReceive;

		public StunClient(StunServer server, Socket socket)
		{
			_server = server;
			_socket = socket;

			_socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveData), null);
		}

		public IPEndPoint RemoteEndpoint
		{
			get { return (IPEndPoint)_socket.RemoteEndPoint; }
		}

		private void ReceiveData(IAsyncResult ar)
		{
			int count = _socket.EndReceive(ar);
			_dataStream.Position = _dataStream.Length;
			_dataStream.Write(_buffer, 0, count);

			Extract();

			_socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveData), null);
		}

		private void Extract()
		{
			_dataStream.Position = 0;
			BinaryReader br = new BinaryReader(_dataStream);

			if (_dataStream.Length >= HeaderSize)
			{
				int length = br.ReadInt32();

				if (_dataStream.Position + length <= _dataStream.Length)
				{
					byte[] data = new byte[length];
					_dataStream.Read(data, 0, length);

					if(OnMessageReceive != null) OnMessageReceive(this, data);

					// remove used data
					MemoryStream stream = new MemoryStream();
					while (_dataStream.Position < _dataStream.Length)
					{
						int count = _dataStream.Read(_buffer, 0, _buffer.Length);
						stream.Write(_buffer, 0, count);
					}
					_dataStream = stream;
				}
			}
		}

		public void Send(IStunMessage msg)
		{
			byte[] buffer = msg.GetBytes();

			MemoryStream stream = new MemoryStream();
			BinaryWriter bw = new BinaryWriter(stream);
			bw.Write(buffer.Length);
			bw.Write(buffer);

			_socket.Send(stream.ToArray());
		}
	}
}